import uuidv4 from 'uuid/v4'
import { EventEmitter } from '../../base/events'
import {AppModel} from '../AppModel'
import { Wire } from '../conn/Wire'
import { PartFigure } from '../../graph/PartFigure'
import { conn } from '@beshell/conn-ts/Connection'
import { Cable } from '../conn/Cable'
import { CableClass } from '../conn/CableClass'
import CablePort from '../conn/CablePort'
import { AllGPIOUsages, GPIOMode, AllSerialUsages, PWMTimerSetting, Stud } from '../Stud'
import { Usage } from '../StudUsage'
import { I2C } from "../conn/I2C"
import { SPI } from "../conn/SPI"
import { UART } from "../conn/UART"
import { sleep } from '../../base/utils'

declare var __dirname: string

export interface IPwmPart {
    setPWMValue(stud: Stud, value: number) : void
}

export class PartBase extends EventEmitter {
    
    public uuid: string = ''

    // 在 model 内同类型 part 的 instanceIdx 递增
    public instanceIndex = -1

    // 实例名称
    private _instanceName = ''
    public get instanceName() {
        return this._instanceName
    }
    public set instanceName(name:string) {
        this._instanceName = name
        this.emit("changed:instanceName", name)
    }

    public get title(): string {
        return this.json?.title
    }

    public name: string = ""

    public studs: Stud[][] = []
    protected mapStuds: {[key:string]: Stud} = {}

    public figure?: PartFigure

    constructor(public model:AppModel, public json:any) {
        super()
        this._eventFilters = ["changed:instanceName"]

        if(!model) {
            console.error(new Error)
            debugger
        }

        // uuid 尽可能短
        let uuid = uuidv4()
        for(let i=2;i<uuid.length;i++) {
            let _u = uuid.substr(0,i)
            if( !model.findPartFromUUID(_u) ){
                this.uuid = _u
                break
            }
        }

        // this.name = name
        this._instanceName = this.model.makePartInstanceName(this)

        // 计算 instanceIndex
        let lastIns = model.reducePart((prev, part)=>{
            return part
        }, this.constructor as any)
        this.instanceIndex = lastIns? (lastIns.instanceIndex + 1): 0
    }

    // 子类中调用，在 super 之后调用，确保对象属性已经创建
    protected loadJson() {
        try{
            if(this.json.studs) {
                for(var r=0;r<this.json.studs.length; r++){
                    this.studs[r] = []
                    for(var c=0;c<this.json.studs[r].length; c++){
                        this.studs[r][c] = new Stud(this.json.studs[r][c], this)

                        this.studs[r][c].placement[0] = r
                        this.studs[r][c].placement[1] = c

                        if(this.json.studs[r][c].name) {
                            if(this.mapStuds[this.json.studs[r][c].name]) {
                                console.error("重复的 stud name: ", this.json.studs[r][c].name, ", part:", this.title)
                                console.error(r,c, this.json.studs[r][c])
                            }
                            this.mapStuds[this.json.studs[r][c].name] = this.studs[r][c]
                        }
                    }
                }
            }
        } catch(e) {
            console.error(e)
        }
    }

    findStudFromName(name: string): Stud|undefined {
        return this.mapStuds[name]
    }
    
    // defaultStud(usage:Usage): Stud|undefined {
    //     if( this.json && this.json.defaultStuds && this.json.defaultStuds[usage]) {
    //         return this.mapStuds[ this.json.defaultStuds[usage] ]
    //     }
    // }

    // autoWireToMaster(stud: Stud, usage: Usage): Wire|undefined {
    //     if(!stud) { debugger }
    //     if( !this.model || !this.model.masterPart)
    //         return 
        
    //     let masterStud = this.model.masterPart.defaultStud(usage)
    //     if(!masterStud) {
    //         return
    //     }
    //     return this.model.createWire(stud, masterStud)
    // }

    public eachStud(callback: (stud:Stud, row: number, column: number)=>void) {
        let ret = undefined
        for(var r=0;r<this.studs.length; r++) {
            for(var c=0;c<this.studs[r].length; c++) {
                ret = callback(this.studs[r][c],r,c)
                if(ret!=undefined)
                    return ret
            }
        }
    }

    public eachWire(callback: (wire:Wire)=>void) {
        for(let r of this.studs) {
            for(let stud of r) {
                stud.wires.forEach(callback)
            }
        }
    }

    public destroy() {
        this.eachStud((s: Stud) => {
            s.destroy()
        })

        this.mapStuds = {}
        this.studs.length = 0
        delete this.json
    }

    public serialize(): any {
        let doc: any = {
            uuid: this.uuid ,
            clazz: this.constructor.name ,
        }
        if(this._instanceName) {
            doc.insname = this._instanceName
        }
        return doc
    }

    public unserialize(json: any) {
        this.uuid = json.uuid
        if(json.insname) {
            this._instanceName = json.insname
        }
    }

    public generateSetupCode(indent=0){
        return ''
    }
    public generateBeginCode(indent=0){
        return ''
    }
}



export abstract class PartSlaver extends PartBase {

    constructor(model: AppModel, json:any){
        super(model, json)

        this.loadJson()
    }

    public mapCablePorts: {[key:string]: CablePort} = {}

    public remoteVar() {
        return `be.part["${this.instanceName}"]`
    }
    
    // 在远程零件上执行 beconsole 的相关初始化
    public remoteSetup() {}
    
    public maxBaudrate(serial: Cable, stud: Stud): number {
        return -1
    }

    // 检查是否所有连线已经就绪
    public isConnectionReady() {
        return false
    }

    // 当该零件的某个 stud 连接的 Provider 发生变化
    public onStudProviderChanged(stud: Stud, provider: Stud|null, oldProvider: Stud|null) {
        // console.log("onStudProviderChanged", this, stud, oldProvider, "->", provider)
        if( conn.isConnected && this.isConnectionReady()) {
            let code = this.generateBeginCode()
            if(code)
                conn.console.run(code)
        }
    }

    registerCableStud(cableClazz: CableClass, stud: Stud) {
        if(!this.mapCablePorts[cableClazz.name])
            this.mapCablePorts[cableClazz.name] = new CablePort(cableClazz)
        this.mapCablePorts[cableClazz.name].mapStuds[stud.usage] = stud
        stud.cablePort = this.mapCablePorts[cableClazz.name]
    }

    findCable(cableClazz: CableClass): Cable|null {
        let port = this.mapCablePorts[cableClazz.name]
        if(!port || !this.model?.masterPart)
            return null
        return this.model.masterPart.allocCable(port)
    }
}



const mapGpioSerial: {[key:string]: string[]} = {}
mapGpioSerial[Usage.uartmosi] = ['o']
mapGpioSerial[Usage.uartmiso] = ['i']
mapGpioSerial[Usage.i2cscl] = ['o']
mapGpioSerial[Usage.i2csda] = ['i', 'o']
mapGpioSerial[Usage.spisck] = ['o']
mapGpioSerial[Usage.spimosi] = ['o']
mapGpioSerial[Usage.spimiso] = ['i']


function checkGpioForSerial(serailUsage: string, gpioUsages: string[]): boolean {
    for(var gpio of mapGpioSerial[serailUsage]) {
        if( !gpioUsages.includes(gpio) )
            return false ;
    }
    return true
}

export abstract class PartHost extends PartBase implements IPwmPart {

    public uartLines: UART[] = []
    public i2cLines: I2C[] = []
    public spiLines: SPI[] = []

    private mapCables = {
        'UART': this.uartLines ,
        'I2C': this.i2cLines ,
        'SPI': this.spiLines ,
    }


    constructor(model:AppModel, json: any) {
        super(model, json)

        this.loadJson()
        // this.ap.ssid+= "@" + this.uuid.substr(0,5).toUpperCase()
        
        // 初始各个 Stud
        for(var r=0;r<this.studs.length;r++) {
            for(var c=0;c<this.studs[r].length;c++) {
                let stud = this.studs[r][c]

                // 补充 gpio 的串口用途 
                for(var serailUsage in mapGpioSerial) {
                    if(checkGpioForSerial(serailUsage, stud.optUsages)) {
                        stud.optUsages.push(serailUsage as Usage)
                    }
                }
                
                // 将 none 放在最前面
                if( stud.optUsages.removeFirst(Usage.none) ) {
                    stud.optUsages.unshift(Usage.none)
                }

                // 设置 pwm
                if( stud.optUsages.includes(Usage.pwm) ) {
                    stud.pwm.setting = new PWMTimerSetting()
                    stud.pwm.setting.frequency = 50
                    stud.pwm.setting.resolution = 16

                    stud.on("pwm.value.set", this.setPWMValue.bind(this))
                }

                stud.on("usage.changed", this.onUsageChanged.bind(this))
                stud.on("mode.changed", this.onGPIOModeChanged.bind(this))
                stud.on("output.set", this.onGPIOValueSet.bind(this))
                
                // 事件冒泡
                stud.on("*", (eventName: string, ...args: any[])=>{
                    eventName = "stud."+eventName
                    this.emit(eventName, ...args)
                    this.model && this.model.emit(eventName, ...args)
                })
            }
        }

        ;(window as any).master = this
    }

    allocCable(device: CablePort) {

        let lines: Cable[] = (this.mapCables as any)[device.cableClass.name]
        if(!lines) {
            return null
        }
        let firstFree = null
        for(let cable of lines) {

            if( cable.isOnline(device) ){
                return cable
            }

            // 首个空闲 cable
            if( !firstFree && cable.isFree() ) {
                firstFree = cable
            }
        }

        if(firstFree) {
            device.takeCable(firstFree)
        }

        return firstFree
    }

    async setup() {
        if(conn.isConnected) {
            return
        }

        // serial 线路
        for(let cable of this.uartLines) {
            await cable.setup()
        }
        for(let cable of this.i2cLines) {
            await cable.setup()
        }
        for(let cable of this.spiLines) {
            await cable.setup()
        }

        // gpio
        for(let row of this.studs) {
            for(let stud of row) {
                await this.setupStud(stud)
            }
        }
    }

    async setupStud(stud: Stud) {
        await this.setupGPIO(stud)
    }

    async setupGPIO(stud: Stud): Promise<boolean> {

        if( !AllGPIOUsages.includes(stud.usage) ) 
            return false

        // pwm 设置
        if(stud.usage==Usage.pwm) {
            await this.setupPWM(stud)
        } else {
            console.log("pwmStop")
            await conn.console.call(`pin(${stud.info.name}).pwmStop()`)
        }

        // input watch 事件
        if(stud.usage==Usage.gpioinput){
            // 立即读取当前电平量
            stud.gpio.changeValue(await this.readPin(stud))

            console.log("input watch")
            // await connection.console.call(`beconsoled.watch(${stud.info.name})`)
        } else {
            console.log("unwatch")
            // await connection.console.call(`beconsoled.unwatch(${stud.info.name})`)
        }

        // gpio 模式
        await this.setupGPIOMode(stud)

        return true
    }
    async setupPWM(stud: Stud) {
        await conn.console.run(`pin(${stud.info.pin}).pwm(${stud.pwm.value}, ${stud.pwm.setting?.frequency}, ${stud.pwm.setting?.resolution})`)
    }

    async setupGPIOMode(stud: Stud) {
        await conn.console.run(`pinMode(${stud.info.pin}, "${stud.gpio.mode}") \n`)
        await sleep(1)
    }
    
    async readPin(stud: Stud) {
        return await conn.console.call(`digitalRead(${stud.info.name})`)
    }

    setPWMValue(stud: Stud, percent: number): void {
        console.log("xxxxx")
        conn.console.run(`pin(${stud.info.pin}).pwm(${percent})`)
    }
    setPWMFrequency(stud: Stud, frequency: number) {
        if(!stud.pwm.setting) {
            return
        }
        stud.pwm.setting.frequency = frequency
        this.setupPWM(stud)
    }
    setPWMResolution(stud: Stud, resolution: number) {
        if(!stud.pwm.setting) {
            return
        }
        stud.pwm.setting.resolution = resolution
        this.setupPWM(stud)
    }


    public allSerialsWithUsage(usage: Usage) : Cable[] | null{
        
        if( AllSerialUsages.isUART(usage) ) {
            return this.uartLines
        }
        else if ( AllSerialUsages.isI2C(usage) ) {
            return this.i2cLines
        }
        else if ( AllSerialUsages.isSPI(usage) ) {
            return this.spiLines
        }
        else {
            return null
        } 
    }

    public serialize() {
        let doc = super.serialize()

        // studs
        let docStuds: any = []
        this.eachStud(stud=>{
            let data = stud.serialize()
            if(!data)
                return
            docStuds.push(data)
        })
        if(docStuds.length>0) {
            doc.studs = docStuds
        }

        // cables
        let mapCables: any = {}
        this.uartLines.concat(this.i2cLines as any[]).concat(this.spiLines as any[])
            .forEach( cable => {
                let json = cable.serialize()
                if(json)
                    mapCables[cable.name] = json
            } )
        if(Object.keys(mapCables).length) {
            doc.cables = mapCables
        }

        return doc
    }

    
    public unserialize(json: any) {
        super.unserialize(json)

        // studs
        if(json.studs) {
            for(let studJson of json.studs) {
                let stud = this.findStudFromName(studJson.name)
                stud?.unserialize(studJson)
            }
        }

        // cables
        if(json.cables) {
            this.uartLines.concat(this.i2cLines as any[]).concat(this.spiLines as any[])
                .forEach(cable=>{
                    if(json.cables[cable.name]) {
                        cable.unserialize( json.cables[cable.name], this )
                    }
                })
        }

    }

    async onUsageChanged(stud: Stud, newUsage: Usage, oldUsage: Usage) {

        // 自动分配 串口线路
        // if(!stud.serial) {
        //     let lstserials = this.allSerialsWithUsage(newUsage)
        //     if(lstserials) {
        //         for(let serial of lstserials) {
        //             if( !serial.hostStud(newUsage) ) {
        //                 serial.setHostStud(newUsage, stud)
        //                 if( connection.isConnected )
        //                     serial.setup()
        //                 break
        //             }
        //         }
        //     }
        // }

        // 默认的 GPIO 模式
        if(newUsage==Usage.gpiooutput) {
            stud.gpio.setModeWithoutEvent(GPIOMode.output)
        }
        else if(newUsage==Usage.gpioinput) {
            stud.gpio.setModeWithoutEvent(GPIOMode.input_pullup)
        }

        // 设置 实体零件
        return this.setupStud(stud)
    }

    onGPIOModeChanged(stud: Stud, newMode: GPIOMode, oldMode: GPIOMode) {
        this.setupGPIOMode(stud)
    }

    onGPIOValueSet(stud: Stud, value: number) {
        conn.console.run(`digitalWrite(${stud.info.pin}, ${value?1:0}) \n`)
    }
    
    public generateSetupCode(indent=0){
        let code = ''
        for(let serial of this.i2cLines) {
            if(serial.isValid()) {
                code+= serial.generateSetupCode(indent) + "\r\n"
            }
        }
        for(let serial of this.spiLines) {
            if(serial.isValid()) {
                code+= serial.generateSetupCode(indent) + "\r\n"
            }
        }
        return code
    }
    
    public generateBeginCode(){
        return ''
    }
}